﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using TemplateEngine;
using Common;
using Common.Logging;

namespace WebServer
{
    public class FilesHandler : BaseRequestHandler
    {
        private static ILog log = LogManager.GetCurrentClassLogger();

        private bool _allowCaching = true;
        public bool AllowCaching
        {
            get { return _allowCaching; }
            set { _allowCaching = value; }
        }

        public FilesHandler(DirectoryInfo root)
        {
            _root = root;
            if (!_root.Exists)
            { throw new DirectoryNotFoundException(_root.FullName); }
            init();
        }
        public FilesHandler(String root)
        {
            _root = new DirectoryInfo(root);
            if (!_root.Exists)
            { throw new DirectoryNotFoundException(_root.FullName); }
            init();
        }
        private void init()
        {
            SetupDefaultMimeTypes();
        }

        private String GetMimeType(String file)
        { return GetMimeType(new FileInfo(file)); }
        private String GetMimeType(FileInfo file)
        {
            String ext = file.Extension;
            ext = ext.ToLower().Trim();
            String mime = null;
            _mimeTypes.TryGetValue(ext, out mime);
            return mime;
        }

        private DirectoryInfo _root;
        private Dictionary<String, String> _mimeTypes = new Dictionary<string, string>();
        public void AddMimeType(String extension, String mimeType)
        {
            _mimeTypes.Add(extension.ToLower().Trim(), mimeType.Trim());
        }
        public void RemoveMimeType(String extension)
        {
            _mimeTypes.Remove(extension.ToLower().Trim());
        }
        private void SetupDefaultMimeTypes()
        {
            AddMimeType(".gif", "image/gif");
            AddMimeType(".jpg", "image/jpeg");
            AddMimeType(".jpeg", "image/jpeg");
            AddMimeType(".png", "image/png");
            AddMimeType(".js", "application/x-javascript");
            AddMimeType(".htm", "text/html");
            AddMimeType(".html", "text/html");
            AddMimeType(".css", "text/css");
            AddMimeType(".txt", "text/plain");
            AddMimeType(".zip", "application/zip");
        }

        private bool _showDirectoryListing = true;
        public bool ShowDirectoryListing
        { get { return _showDirectoryListing; } set { _showDirectoryListing = value; } }

        private static readonly ITemplate DefaultDirectoryListingTemplate = ResourceTemplateFactory.GetTemplateNoCache("WebServer.Templates.defaultDirectoryListing.template");
        private ITemplate _error404Template = ResourceTemplateFactory.GetTemplateNoCache("WebServer.Templates.404FileNotFound.template");

        private ITemplate _directoryListingTemplate = DefaultDirectoryListingTemplate;
        public ITemplate DirectoryListingTemplate
        {
            get { return _directoryListingTemplate; }
            set { _directoryListingTemplate = value; }
        }

        public override void Get(System.Net.HttpListenerRequest request, System.Net.HttpListenerResponse response)
        {
            String path = Uri.UnescapeDataString(request.Url.AbsolutePath) ?? "";
            if (path.Contains("~"))
            {
                path = path.Substring(path.LastIndexOf("~") + 1);
            }
            path = path.Replace("..\\", "").Replace("/","\\").Trim('\\');
            String fullPath = Path.Combine(_root.FullName, path);

            bool isFile = File.Exists(fullPath);
            bool isDir = Directory.Exists(fullPath);


            if (isFile)
            {
                String modifiedSinceString = request.Headers["If-Modified-Since"];
                DateTime modifiedSince = DateTime.MinValue; ;
                if (modifiedSinceString != null)
                {
                    DateTime.TryParse(modifiedSinceString, out modifiedSince);
                }
                

                FileInfo file = new FileInfo(fullPath);

                AddCachingHeaders(response, file);

                response.ContentType = GetMimeType(file);
                if (modifiedSince >= file.LastWriteTime)
                {
                    response.StatusCode = 304;
                    log.Trace("304 FILE - " + fullPath);
                }
                else
                {
                    byte[] temp = File.ReadAllBytes(fullPath);
                    response.OutputStream.Write(temp, 0, temp.Length);
                    log.Trace("200 FILE - " + fullPath);
                }
            }
            else if (isDir)
            {
                AddCachingHeaders(response, new DirectoryInfo(fullPath));
                using (StreamWriter sw = new StreamWriter(response.OutputStream))
                { sw.Write(getDirectoryListing(request.Url, new DirectoryInfo(fullPath))); }
                log.Trace("200 DIRECTORY - " + fullPath);
            }
            else
            {
                response.StatusCode = 404;
                using (StreamWriter sw = new StreamWriter(response.OutputStream))
                { sw.Write(get404(request.Url)); }
                log.Trace("404 - " + fullPath);
            }
        }
        private void AddCachingHeaders(System.Net.HttpListenerResponse response, FileSystemInfo info)
        {
            if (!_allowCaching)
            { return; }
            response.Headers["Cache-Control"] = "pubic, max-age=86400";
            response.Headers["Expires"] = DateTime.Now.AddDays(1).ToLongDateTimeString();
            response.Headers["Last-Modified"] = info.LastWriteTime.AddSeconds(1).ToLongDateTimeString();
        }
        

        private String get404(Uri uri)
        {
            Dictionary<String, Object> data = new Dictionary<string, object>();
            data.Add("url", uri);
            return _error404Template.Process(data);
        }
        private String getDirectoryListing(Uri url, DirectoryInfo dir)
        {
            Dictionary<String, Object> data = new Dictionary<string, object>();
            data.Add("url", url);
            data.Add("folder", dir);
            data.Add("subfolders", dir.GetDirectories());
            data.Add("files", dir.GetFiles());
            return DirectoryListingTemplate.Process(data);
        }
    }
}
